第07章 K近邻算法¶

7.1 K近邻算法的原理和代码实现¶

K近邻算法 (K-Nearest Neighbor, 又称KNN算法)

7.1.1 K近邻算法的基本原理¶

  • K近邻算法的原理

    • 对于一个新样本, K近邻算法的目的就是在已有数据中寻找与它最相似的K个数据, 或者说 "离它最近" 的K个数据, 如果这K个数据大多数属于某个类别, 则该样本也属于这个类别
  • 两个数据的相似度(距离)

    • 欧氏距离计算公式$$|AB| = \sqrt{\sum(X_i - Y_i)^2}$$

7.1.3 K近邻算法的代码实现¶

In [1]:
import warnings

warnings.filterwarnings('ignore')
In [2]:
import pandas as pd
df = pd.read_excel('数据/葡萄酒.xlsx')

print(df)

print(df.columns)
  原始样本  酒精含量(%)  苹果酸含量(%)  分类
0  样本1        5         2   0
1  样本2        6         1   0
2  样本3        4         1   0
3  样本4        8         3   1
4  样本5       10         2   1
Index(['原始样本', '酒精含量(%)', '苹果酸含量(%)', '分类'], dtype='object')
In [3]:
# 特征变量和目标变量的切分
X_train = df[['酒精含量(%)','苹果酸含量(%)']]
y_train = df['分类']  
In [4]:
# 模型训练
from sklearn.neighbors import KNeighborsClassifier as KNN
knn = KNN(n_neighbors=3)  
knn.fit(X_train, y_train)
Out[4]:
KNeighborsClassifier(n_neighbors=3)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier(n_neighbors=3)
In [5]:
# 模型预测:预测单个样本
X_test = [[7, 1]]  # X_test为测试集特征变量
answer = knn.predict(X_test)  
print(answer)
[0]
In [6]:
# 模型预测:预测多个样本
X_test = [[7, 1], [8, 3]]  # 这里能帮助理解为什么要写成二维数组的样式
answer = knn.predict(X_test)  
print(answer)
[0 1]

补充知识点: K近邻算法回归模型¶

  • K近邻算法分类模型将离待预测样本点最近的K个训练样本点中出现次数最多的分类作为待预测样本点的分类,K近邻算法回归模型则将离待预测样本点最近的K个训练样本点的平均值作为待预测样本点的分类
In [7]:
from sklearn.neighbors import KNeighborsRegressor
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 2, 3, 4, 5]

model = KNeighborsRegressor(n_neighbors=2)
model.fit(X, y)

print(model.predict([[5, 5]]))
[2.5]

7.2 手写数字识别模型¶

7.2.2 手写数字识别的原理¶

7.2.2.1 图像二值化

  • 手写数字识别第一步也是最关键的一步

  • 将图片转换为计算机能识别的内容--数字0和1

  • 又称为图像二值化

7.2.2.2 二维数组转换为以为数组

经过图像二值化处理得到的0-1矩阵相当于一个二维数组, 为了方便进行机器学习建模, 还需要对这个二维数组进行简单的处理: 在第1行数字之后一次拼接第2~32行的数字, 得到一个1 $\times$ 1024的一维数组

7.2.3 手写数字识别的代码实现¶

7.2.3.1 读取数据

In [8]:
# 1.读取数据
import pandas as pd
df = pd.read_excel('数据/手写字体识别.xlsx')
In [9]:
print(df.tail())
      对应数字  0  1  2  3  4  5  6  7  8  ...  1014  1015  1016  1017  1018  \
1929     9  0  0  0  0  0  0  0  0  0  ...     0     0     0     0     0   
1930     9  0  0  0  0  0  0  0  0  0  ...     0     0     0     0     0   
1931     9  0  0  0  0  0  0  0  0  0  ...     0     0     0     0     0   
1932     9  0  0  0  0  0  0  0  0  0  ...     0     0     0     0     0   
1933     9  0  0  0  0  0  0  0  0  0  ...     1     1     0     0     0   

      1019  1020  1021  1022  1023  
1929     0     0     0     0     0  
1930     0     0     0     0     0  
1931     0     0     0     0     0  
1932     0     0     0     0     0  
1933     0     0     0     0     0  

[5 rows x 1025 columns]

7.2.3.2 提取特征变量和目标变量

In [10]:
X = df.drop(columns='对应数字') 
y = df['对应数字']

!!!

不标准化处理效果更好, 原因是标准化处理后所有数字接近0, 此时每张训练图片的一维化结果都接近一个1×1024的0数组, 这个数组离 '1' 的距离最近

In [11]:
#标准化处理

# from sklearn.preprocessing import StandardScaler

# X = StandardScaler().fit_transform(X)

7.2.3.3 划分训练集和测试集

In [12]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)

7.2.3.4 模型搭建

In [13]:
from sklearn.neighbors import KNeighborsClassifier as KNN
knn = KNN(n_neighbors=5) 
knn.fit(X_train, y_train)
Out[13]:
KNeighborsClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier()
In [14]:
y_pred = knn.predict(X_test)
print(y_pred[0:100])
[5 3 7 8 9 2 1 9 5 8 9 5 9 3 3 2 3 7 9 1 0 0 7 6 6 7 0 9 6 9 1 8 6 9 2 5 2
 4 5 8 3 6 9 4 9 2 7 3 4 9 5 6 7 3 3 8 3 1 5 3 6 7 5 0 3 7 1 4 9 1 5 1 2 6
 9 1 9 5 5 9 2 8 8 4 4 9 4 3 9 8 0 3 4 3 6 8 5 2 9 0]
In [15]:
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
print(a.head(20))
    预测值  实际值
0     5    5
1     3    3
2     7    7
3     8    8
4     9    9
5     2    2
6     1    1
7     9    4
8     5    5
9     8    8
10    9    5
11    5    5
12    9    9
13    3    3
14    3    3
15    2    2
16    3    3
17    7    7
18    9    9
19    1    1
In [16]:
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)

# 模型自带的score()函数也可以进行打分
score = knn.score(X_test, y_test)
print(score)
0.979328165374677
0.979328165374677

7.3 图像识别原理详解¶

7.3.1 图片大小调整及显示¶

In [17]:
from PIL import Image #引入Pillow库中的Image模块来处理图像
img = Image.open('测试图片/数字4.png') #open()函数可以打开JPG、PNG等格式的图片
img = img.resize((32,32)) #resize()函数可以调整图像大小,这里调整为32×32像素
# img.show() #用show()函数显示图片
img
Out[17]:
No description has been provided for this image

7.3.2 图片灰度处理¶

In [18]:
img = img.convert('L') #对原始图片进行灰度处理
img
Out[18]:
No description has been provided for this image

7.3.3 图片二值化处理¶

In [19]:
import numpy as np

img_new = img.point(lambda x: 0 if x > 200 else 1) #point()函数可以操控每一个像素点, point()函数中传入的内容为 lambda 匿名函数, 其含义为将色彩数值大于 128 的像素点赋值为0, 反之赋值为1; 图像在进行灰度处理后, 每一个像素点由一个取值范围为0~255的数字表示, 其中0代表黑色, 255代表白色, 所以这里以128为阈值进行划分, 即原来偏白色的区域赋值为0, 原来偏黑色的区域赋值为1

arr = np.array(img_new) #将已经转换成数字0和1的32×32像素的图片转换为32×32的二维数组
In [20]:
for i in range(arr.shape[0]):
    print(arr[i])
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

7.3.4 将二维数组转换为一维数组¶

In [21]:
arr_new = arr.reshape(1, -1) #转换成一行, 若写成reshape(-1,1)则转换成一列

print(arr_new.shape)
(1, 1024)
In [22]:
answer = knn.predict(arr_new)

print('图片中的数字为:' + str(answer[0]))
图片中的数字为:4

测试¶

函数封装¶

In [23]:
from PIL import Image #引入Pillow库中的Image模块来处理图像
# img = Image.open('测试图片2.png') #open()函数可以打开JPG、PNG等格式的图片
# img = img.resize((32,32)) #resize()函数可以调整图像大小,这里调整为32×32像素
# # img.show() #用show()函数显示图片
# img

def handwrite_to_num(img):

    img = img.resize((32,32))

    img = img.convert('L') #对原始图片进行灰度处理

    img_new = img.point(lambda x: 0 if x > 200 else 1) #point()函数可以操控每一个像素点, point()函数中传入的内容为 lambda 匿名函数, 其含义为将色彩数值大于 128 的像素点赋值为0, 反之赋值为1; 图像在进行灰度处理后, 每一个像素点由一个取值范围为0~255的数字表示, 其中0代表黑色, 255代表白色, 所以这里以128为阈值进行划分, 即原来偏白色的区域赋值为0, 原来偏黑色的区域赋值为1

    arr = np.array(img_new) #将已经转换成数字0和1的32×32像素的图片转换为32×32的二维数组

    # for i in range(arr.shape[0]):
    #   print(arr[i])

    arr_new = arr.reshape(1, -1) #转换成一行, 若写成reshape(-1,1)则转换成一列

    # print(arr_new.shape)

    answer = knn.predict(arr_new)

    print(answer)

    return answer[0]

    # print('图片中的数字为:' + str(answer[0]))
In [24]:
img = Image.open('测试图片/数字4.png')

print(handwrite_to_num(img))
[4]
4